package ldk.util.refresh;

import android.content.Context;
import android.support.annotation.Nullable;
import android.support.annotation.Size;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.NestedScrollingChild;
import android.support.v4.view.NestedScrollingChildHelper;
import android.support.v4.view.NestedScrollingParent;
import android.support.v4.view.NestedScrollingParentHelper;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;


/**
 * Just like v4 SwipeRefreshLayout
 * Created by ldkxingzhe@163.com on 17/3/28.
 */
public class RefreshLayout extends ViewGroup implements NestedScrollingParent, NestedScrollingChild {
    @SuppressWarnings("unused")
    private static final String TAG = "RefreshLayout";

    private static final int INVALID_POINTER = -1;

    private View mTarget;  // the target of the gesture
    private int mTouchSlop;
    private float mTotalDragDistance = -1;

    // if nested scrolling is enabled, the total amount that needed to be
    // consumed by this as the nested scrolling parent is used in place of the
    // overscroll determined by MOVE events in the onTouch handler
    private float mTotalUnConsumed;
    private final NestedScrollingParentHelper mNestedScrollingParentHelper;
    private final NestedScrollingChildHelper mNestedScrollingChildHelper;
    private final int[] mParentScrollConsumed = new int[2];
    private final int[] mParentOffsetInWindow = new int[2];

    private int mCurrentTargetOffsetTop;

    private float mInitialDownAxisValue, mLastDownAxisValue;
    private boolean mIsBeingDragged;
    private int mActivePointerId = INVALID_POINTER;

    private int mOrientation = ORIENTATION_LEFT;

    public static final int ORIENTATION_LEFT = 1;
    public static final int ORIENTATION_TOP = 1 << 1;
    public static final int ORIENTATION_RIGHT = 1 << 2;
    public static final int ORIENTATION_BOTTOM = 1 << 3;

    private View mHeaderView;
    private OnTouchListener mOnTouchListener;

    private RefreshManager mRefreshManager;


    public RefreshLayout(Context context) {
        this(context, null);
    }

    public RefreshLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        setWillNotDraw(false);
        ViewCompat.setChildrenDrawingOrderEnabled(this, true);
        mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);
        mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);
        setNestedScrollingEnabled(true);
    }

    /**
     * 设置refreshLayout的位置
     * @see #ORIENTATION_TOP
     */
    public void setOrientation(int orientation){
        mOrientation = orientation;
    }

    public void setRefreshManager(@Nullable RefreshManager manager){
        mRefreshManager = manager;
        if (mRefreshManager != null){
            mRefreshManager.bindRefreshLayout(this);
        }
    }

    public void setHeaderView(@Nullable View headerView){
        if (mHeaderView != null){
            removeView(mHeaderView);
        }
        mHeaderView = headerView;
        if (mHeaderView != null){
            addView(mHeaderView);
        }
    }


    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mOnTouchListener != null) mOnTouchListener.onTouch(this, ev);
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (mRefreshManager == null) super.onInterceptTouchEvent(ev);

        final int action = MotionEventCompat.getActionMasked(ev);
        int pointerIndex;

        // position not right
        float translationX = ViewCompat.getTranslationX(mTarget);
        float translationY = ViewCompat.getTranslationY(mTarget);
        if (translationX != 0
                || translationY != 0)
            return isEnabled();

        if (!isEnabled() || canChildScroll()){
            return false;
        }

        switch (action){
            case MotionEvent.ACTION_DOWN:
                mActivePointerId = ev.getPointerId(0);
                mIsBeingDragged = false;

                pointerIndex = ev.findPointerIndex(mActivePointerId);
                if (pointerIndex < 0) return false;
                mInitialDownAxisValue = isHorizontal() ? ev.getX(pointerIndex) : ev.getY(pointerIndex);
                mLastDownAxisValue = mInitialDownAxisValue;
                startNestedScroll(isHorizontal() ? ViewCompat.SCROLL_AXIS_HORIZONTAL : ViewCompat.SCROLL_AXIS_VERTICAL);
                break;
            case MotionEvent.ACTION_MOVE:
                if (mActivePointerId == INVALID_POINTER){
                    Log.e(TAG, "Got ACTION_MOVE event but don't have an active id");
                    return false;
                }

                pointerIndex = ev.findPointerIndex(mActivePointerId);
                if (pointerIndex < 0){
                    return false;
                }

                final float downValue = isHorizontal() ? ev.getX(pointerIndex) : ev.getY(pointerIndex);
                startDragging(downValue);
                break;
            case MotionEventCompat.ACTION_POINTER_UP:
                onSecondaryPointerUp(ev);
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                stopNestedScroll();
                mIsBeingDragged = false;
                mActivePointerId = INVALID_POINTER;
                break;
        }
        Log.d(TAG, "onIntercept: " + mIsBeingDragged);
        return mIsBeingDragged;
    }

    private boolean isHorizontal(){
        return mOrientation == ORIENTATION_LEFT || mOrientation == ORIENTATION_RIGHT;
    }

    private boolean canChildScroll(){
        switch (mOrientation){
            case ORIENTATION_LEFT:
                return canChildScrollLeft();
            case ORIENTATION_RIGHT:
                return canChildScrollRight();
            case ORIENTATION_TOP:
                return canChildScrollTop();
            case ORIENTATION_BOTTOM:
                return canChildScrollBottom();
        }
        return  false;
    }

    private boolean canChildScrollLeft() {
        return ViewCompat.canScrollHorizontally(mTarget, -1);
    }

    private boolean canChildScrollBottom() {
        return ViewCompat.canScrollVertically(mTarget, 1);
    }

    private boolean canChildScrollTop() {
        return ViewCompat.canScrollVertically(mTarget, -1);
    }

    private boolean canChildScrollRight(){
        return ViewCompat.canScrollHorizontally(mTarget, 1);
    }

    @Override
    public boolean canScrollVertically(int direction) {
        boolean result;
        if (mRefreshManager != null){
            result =  mRefreshManager.canScrollVertically(direction)
                    || super.canScrollVertically(direction);
        }else{
            result = super.canScrollVertically(direction);
        }
        Log.v(TAG, "canScrollVertically " + result);
        return result;
    }

    @Override
    public boolean canScrollHorizontally(int direction) {
        if (mRefreshManager != null){
            return mRefreshManager.canScrollHorizontally(direction)
                    || super.canScrollHorizontally(direction);
        }
        return super.canScrollHorizontally(direction);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (mRefreshManager == null) return super.onTouchEvent(ev);

        final int action = MotionEventCompat.getActionMasked(ev);
        int pointerIndex;
        if (!isEnabled()) return false;

        switch (action){
            case MotionEvent.ACTION_DOWN:
                mActivePointerId = ev.getPointerId(0);
                mIsBeingDragged = false;
                mLastDownAxisValue = isHorizontal() ? ev.getX(0) : ev.getY(0);
                startNestedScroll(isHorizontal() ? ViewCompat.SCROLL_AXIS_HORIZONTAL : ViewCompat.SCROLL_AXIS_VERTICAL);
                break;
            case MotionEvent.ACTION_MOVE:
                pointerIndex = ev.findPointerIndex(mActivePointerId);
                if (pointerIndex < 0){
                    Log.e(TAG, "Got ACTION_MOVE event but don't have an active pointer id");
                    return false;
                }
                final float downValue = isHorizontal() ? ev.getX(pointerIndex) : ev.getY(pointerIndex);
                startDragging(downValue);
                if (mIsBeingDragged
                        || ViewCompat.getTranslationX(mTarget) != 0
                        || ViewCompat.getTranslationY(mTarget) != 0){
                    boolean result =  mRefreshManager.onMove(downValue, mLastDownAxisValue);
                    mLastDownAxisValue = downValue;
                    return result;
                }
                mLastDownAxisValue = downValue;
                break;
            case MotionEventCompat.ACTION_POINTER_DOWN:
                pointerIndex = MotionEventCompat.getActionIndex(ev);
                if (pointerIndex < 0){
                    Log.e(TAG, "Got ACTION_POINTER_DOWN event but have an invalid action index");
                    return false;
                }
                mActivePointerId = ev.getPointerId(pointerIndex);
                break;
            case MotionEventCompat.ACTION_POINTER_UP:
                onSecondaryPointerUp(ev);
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                pointerIndex = ev.findPointerIndex(mActivePointerId);
                if (pointerIndex < 0){
                    Log.e(TAG, "Got ACTION_UP event but don't have an active pointer id");
                    return false;
                }

                if (mIsBeingDragged){
                    final float tmpDownValue = isHorizontal() ? ev.getX(pointerIndex) : ev.getY(pointerIndex);
                    mRefreshManager.onFinish(tmpDownValue, mLastDownAxisValue);
                }
                mIsBeingDragged = false;
                mActivePointerId = INVALID_POINTER;
                stopNestedScroll();
                return false;
        }
        return true;
    }

    public static abstract class RefreshManager{

        RefreshLayout mRefreshLayout;
        boolean mIsEnable = true;
        HeaderViewListener mHeaderViewListener;

        public void setHeaderViewListener(@Nullable HeaderViewListener listener){
            mHeaderViewListener = listener;
        }

        private void bindRefreshLayout(RefreshLayout refreshLayout){
            mRefreshLayout = refreshLayout;
        }

        public void setEnable(boolean isEnable){
            mIsEnable = isEnable;
        }

        public boolean isEnable(){
            return mIsEnable;
        }

        public boolean onFinish(float downValue, float lastDownAxisValue){
            if (mHeaderViewListener != null) mHeaderViewListener.onTouchEnd();
            return false;
        }
        public abstract boolean onMove(float downValue, float lastDownAxisValue);
        public abstract void setRefresh(boolean isRefreshing);

        public boolean canScrollHorizontally(int direction){
            return false;
        }

        public boolean canScrollVertically(int direction){
            return false;
        }

        public RefreshLayout getRefreshLayout(){
            return mRefreshLayout;
        }

        protected void showHeaderView(float translationValue) {
            View mTarget = getTargetView();
            if (isHorizontal()){
                ViewCompat.setTranslationX(mTarget, translationValue);
            }else{
                ViewCompat.setTranslationY(mTarget, translationValue);
            }
            View mHeaderView = getHeaderView();
            if (mHeaderView != null){
                if (isHorizontal()){
                    ViewCompat.setTranslationX(mHeaderView, translationValue);
                }else{
                    ViewCompat.setTranslationY(mHeaderView, translationValue);
                }
                HeaderViewListener mHeaderViewListener = getHeaderViewListener();
                if (mHeaderViewListener != null){
                    mHeaderViewListener.showHeaderView(Math.abs(translationValue));
                }
            }
        }

        protected HeaderViewListener getHeaderViewListener(){
            return mHeaderViewListener;
        }

        public View getTargetView(){
            return mRefreshLayout.mTarget;
        }

        public View getHeaderView(){
            return mRefreshLayout.mHeaderView;
        }

        public int getOrientation(){
            return mRefreshLayout.mOrientation;
        }

        public boolean isHorizontal(){
            return mRefreshLayout.isHorizontal();
        }
    }

    public void setRefresh(boolean isRefreshing){
        if (mRefreshManager != null) mRefreshManager.setRefresh(isRefreshing);
    }

    @Override
    public void setOnTouchListener(OnTouchListener l) {
        mOnTouchListener = l;
    }

    private void onSecondaryPointerUp(MotionEvent ev) {

    }

    private void startDragging(float downValue) {
        final float diff = downValue - mInitialDownAxisValue;
        boolean isEnough = false;
        switch (mOrientation){
            case ORIENTATION_RIGHT:
            case ORIENTATION_BOTTOM:
                isEnough = diff < -mTouchSlop;
                break;
            case ORIENTATION_LEFT:
            case ORIENTATION_TOP:
                isEnough = diff > mTouchSlop;
                break;
        }
        if (isEnough && !mIsBeingDragged){
            mIsBeingDragged = true;
            // progress
            Log.d(TAG, "startDragging: " + downValue);
        }
    }

    private void ensureTarget(){
        if (mTarget == null){
            for (int i = 0; i < getChildCount(); i++){
                View child = getChildAt(i);
                if (!child.equals(mHeaderView)){
                    mTarget = child;
                    break;
                }
            }
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        ensureTarget();
        if (mTarget == null) return;
        int targetHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY);
        int targetWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY);
        mTarget.measure(
                targetWidthMeasureSpec,
                targetHeightMeasureSpec);
        if (mHeaderView != null){
            LayoutParams params = mHeaderView.getLayoutParams();
            int headerWidthMeasureSpec;
            if (params.width > 0){
                headerWidthMeasureSpec = MeasureSpec.makeMeasureSpec(params.width, MeasureSpec.EXACTLY);
            }else{
                headerWidthMeasureSpec = isHorizontal() ?
                    MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.UNSPECIFIED):
                    targetWidthMeasureSpec;
            }
            int headerHeightMeasureSpec;
            if (params.height > 0){
                headerHeightMeasureSpec = MeasureSpec.makeMeasureSpec(params.height, MeasureSpec.EXACTLY);
            }else{
                headerHeightMeasureSpec = isHorizontal() ?
                    targetHeightMeasureSpec :
                    MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.UNSPECIFIED);
            }
            mHeaderView.measure(headerWidthMeasureSpec, headerHeightMeasureSpec);
            Log.d(TAG, "mHeaderView: " + mHeaderView.getMeasuredWidth() + ", " + mHeaderView.getMeasuredHeight());
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int width = getMeasuredWidth();
        final int height = getMeasuredHeight();
        if (getChildCount() == 0) return;
        ensureTarget();
        if (mTarget == null) return;

        final View child = mTarget;
        final int childLeft = getPaddingLeft();
        final int childTop = getPaddingTop();
        final int childWidth = width - getPaddingLeft() - getPaddingRight();
        final int childHeight = height - getPaddingTop() - getPaddingBottom();
        child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);

        if (mHeaderView != null){
            int measuredWidth = mHeaderView.getMeasuredWidth();
            int measureHeight = mHeaderView.getMeasuredHeight();
            switch (mOrientation){
                case ORIENTATION_RIGHT:
                    mHeaderView.layout(width, getPaddingTop(), width + measuredWidth, childTop + childHeight);
                    break;
                case ORIENTATION_LEFT:
                    mHeaderView.layout(-measuredWidth, getPaddingTop(), 0, childTop + childHeight);
                    break;
                case ORIENTATION_BOTTOM:
                    mHeaderView.layout(childLeft, height, childLeft + childWidth, height + measureHeight);
                    break;
                case ORIENTATION_TOP:
                    mHeaderView.layout(childLeft, -measureHeight, childLeft + childWidth, 0);
                    break;
            }
        }
    }

    // NestedScrollingParent
    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        boolean isAxesRight = isHorizontal() ? (nestedScrollAxes & ViewCompat.SCROLL_AXIS_HORIZONTAL) != 0
                :  (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
        return isEnabled() && isAxesRight;
    }


    @Override
    public void onNestedScrollAccepted(View child, View target, int axes) {
        mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes);
        if (isHorizontal()){
            startNestedScroll(axes & ViewCompat.SCROLL_AXIS_HORIZONTAL);
        }else{
            startNestedScroll(axes & ViewCompat.SCROLL_AXIS_VERTICAL);
        }
    }

    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        //ignore
    }

    @Override
    public int getNestedScrollAxes() {
        return mNestedScrollingParentHelper.getNestedScrollAxes();
    }

    @Override
    public void onStopNestedScroll(View child) {
        mNestedScrollingParentHelper.onStopNestedScroll(child);
        stopNestedScroll();
    }

    @Override
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        //ignore
        dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, mParentOffsetInWindow);
    }

    // NestedScrollingChild

    @Override
    public void setNestedScrollingEnabled(boolean enabled) {
        mNestedScrollingChildHelper.setNestedScrollingEnabled(enabled);
    }

    @Override
    public boolean isNestedScrollingEnabled() {
        return mNestedScrollingChildHelper.isNestedScrollingEnabled();
    }

    @Override
    public boolean startNestedScroll(int axes) {
        return mNestedScrollingChildHelper.startNestedScroll(axes);
    }

    @Override
    public void stopNestedScroll() {
        mNestedScrollingChildHelper.stopNestedScroll();
    }

    @Override
    public boolean hasNestedScrollingParent() {
        return mNestedScrollingChildHelper.hasNestedScrollingParent();
    }

    @Override
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable @Size(value = 2) int[] offsetInWindow) {
        return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
    }

    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable @Size(value = 2) int[] consumed, @Nullable @Size(value = 2) int[] offsetInWindow) {
        return mNestedScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
    }

    @Override
    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
        return dispatchNestedPreFling(velocityX, velocityY);
    }

    @Override
    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
        return dispatchNestedFling(velocityX, velocityY, consumed);
    }

    @Override
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
        return mNestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
    }

    @Override
    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
        return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
    }
}
